/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "spellingtable.h"

namespace ime_pinyin
{

#ifdef ___BUILD_MODEL___

	const char SpellingTable::
	kNotSupportList[kNotSupportNum][kMaxSpellingSize + 1] = {"HM", "HNG", "NG"};

	// "" is the biggest, so that all empty strings will be moved to the end
	// _eb mean empty is biggest
	int compare_raw_spl_eb(const void *p1, const void *p2)
	{
		if ('\0' == (static_cast<const RawSpelling *>(p1))->str[0]) {
			return 1;
		}

		if ('\0' == (static_cast<const RawSpelling *>(p2))->str[0]) {
			return -1;
		}

		return strcmp((static_cast<const RawSpelling *>(p1))->str,
		              (static_cast<const RawSpelling *>(p2))->str);
	}

	size_t get_odd_next(size_t value)
	{
		size_t v_next = value;
		while (true) {
			size_t v_next_sqrt = (size_t)sqrt(v_next);

			bool is_odd = true;
			for (size_t v_dv = 2; v_dv < v_next_sqrt + 1; v_dv++) {
				if (v_next % v_dv == 0) {
					is_odd = false;
					break;
				}
			}

			if (is_odd) {
				return v_next;
			}

			v_next++;
		}

		// never reach here
		return 0;
	}

	SpellingTable::SpellingTable()
	{
		need_score_ = false;
		raw_spellings_ = NULL;
		spelling_buf_ = NULL;
		spelling_num_ = 0;
		total_freq_ = 0;
		frozen_ = true;
	}

	SpellingTable::~SpellingTable()
	{
		free_resource();
	}

	size_t SpellingTable::get_hash_pos(const char *spelling_str)
	{
		size_t hash_pos = 0;
		for (size_t pos = 0; pos < spelling_size_; pos++) {
			if ('\0' == spelling_str[pos]) {
				break;
			}
			hash_pos += (size_t)spelling_str[pos];
		}

		hash_pos = hash_pos % spelling_max_num_;
		return hash_pos;
	}

	size_t SpellingTable::hash_pos_next(size_t hash_pos)
	{
		hash_pos += 123;
		hash_pos = hash_pos % spelling_max_num_;
		return hash_pos;
	}

	void SpellingTable::free_resource()
	{
		if (NULL != raw_spellings_) {
			delete [] raw_spellings_;
		}
		raw_spellings_ = NULL;

		if (NULL != spelling_buf_) {
			delete [] spelling_buf_;
		}
		spelling_buf_ = NULL;
	}

	bool SpellingTable::init_table(size_t pure_spl_size, size_t spl_max_num,
	                               bool need_score)
	{
		if (pure_spl_size == 0 || spl_max_num == 0) {
			return false;
		}

		need_score_ = need_score;

		free_resource();

		spelling_size_ = pure_spl_size + 1;
		if (need_score) {
			spelling_size_ += 1;
		}
		spelling_max_num_ = get_odd_next(spl_max_num);
		spelling_num_ = 0;

		raw_spellings_ = new RawSpelling[spelling_max_num_];
		spelling_buf_ = new char[spelling_max_num_ * (spelling_size_)];
		if (NULL == raw_spellings_ || NULL == spelling_buf_) {
			free_resource();
			return false;
		}

		memset(raw_spellings_, 0, spelling_max_num_ * sizeof(RawSpelling));
		memset(spelling_buf_, 0, spelling_max_num_ * (spelling_size_));
		frozen_ = false;
		total_freq_ = 0;
		return true;
	}

	bool SpellingTable::put_spelling(const char *spelling_str, double freq)
	{
		if (frozen_ || NULL == spelling_str) {
			return false;
		}

		for (size_t pos = 0; pos < kNotSupportNum; pos++) {
			if (strcmp(spelling_str, kNotSupportList[pos]) == 0) {
				return false;
			}
		}

		total_freq_ += freq;

		size_t hash_pos = get_hash_pos(spelling_str);

		raw_spellings_[hash_pos].str[spelling_size_ - 1] = '\0';

		if (strncmp(raw_spellings_[hash_pos].str, spelling_str,
		            spelling_size_ - 1) == 0) {
			raw_spellings_[hash_pos].freq += freq;
			return true;
		}

		size_t hash_pos_ori = hash_pos;

		while (true) {
			if (strncmp(raw_spellings_[hash_pos].str,
			            spelling_str, spelling_size_ - 1) == 0) {
				raw_spellings_[hash_pos].freq += freq;
				return true;
			}

			if ('\0' == raw_spellings_[hash_pos].str[0]) {
				raw_spellings_[hash_pos].freq += freq;
				strncpy(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1);
				raw_spellings_[hash_pos].str[spelling_size_ - 1] = '\0';
				spelling_num_++;
				return true;
			}

			hash_pos = hash_pos_next(hash_pos);
			if (hash_pos_ori == hash_pos) {
				return false;
			}
		}

		// never reach here
		return false;
	}

	bool SpellingTable::contain(const char *spelling_str)
	{
		if (NULL == spelling_str || NULL == spelling_buf_ || frozen_) {
			return false;
		}

		size_t hash_pos = get_hash_pos(spelling_str);

		if ('\0' == raw_spellings_[hash_pos].str[0]) {
			return false;
		}

		if (strncmp(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1)
		        == 0) {
			return true;
		}

		size_t hash_pos_ori = hash_pos;

		while (true) {
			hash_pos = hash_pos_next(hash_pos);
			if (hash_pos_ori == hash_pos) {
				return false;
			}

			if ('\0' == raw_spellings_[hash_pos].str[0]) {
				return false;
			}

			if (strncmp(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1)
			        == 0) {
				return true;
			}
		}

		// never reach here
		return false;
	}

	const char *SpellingTable::arrange(size_t *item_size, size_t *spl_num)
	{
		if (NULL == raw_spellings_ || NULL == spelling_buf_ ||
		        NULL == item_size || NULL == spl_num) {
			return NULL;
		}

		qsort(raw_spellings_, spelling_max_num_, sizeof(RawSpelling),
		      compare_raw_spl_eb);

		// After sorting, only the first spelling_num_ items are valid.
		// Copy them to the destination buffer.
		for (size_t pos = 0; pos < spelling_num_; pos++) {
			strncpy(spelling_buf_ + pos * spelling_size_, raw_spellings_[pos].str,
			        spelling_size_);
		}

		if (need_score_) {
			if (kPrintDebug0) {
				printf("------------Spelling Possiblities--------------\n");
			}

			double max_score = 0;
			double min_score = 0;

			// After sorting, only the first spelling_num_ items are valid.
			for (size_t pos = 0; pos < spelling_num_; pos++) {
				raw_spellings_[pos].freq /= total_freq_;
				if (need_score_) {
					if (0 == pos) {
						max_score = raw_spellings_[0].freq;
						min_score = max_score;
					} else {
						if (raw_spellings_[pos].freq > max_score) {
							max_score = raw_spellings_[pos].freq;
						}
						if (raw_spellings_[pos].freq < min_score) {
							min_score = raw_spellings_[pos].freq;
						}
					}
				}
			}

			if (kPrintDebug0) {
				printf("-----max psb: %f, min psb: %f\n", max_score, min_score);
			}

			max_score = log(max_score);
			min_score = log(min_score);

			if (kPrintDebug0)
				printf("-----max log value: %f, min log value: %f\n",
				       max_score, min_score);

			// The absolute value of min_score is bigger than that of max_score because
			// both of them are negative after log function.
			score_amplifier_ = 1.0 * 255 / min_score;

			double average_score = 0;
			for (size_t pos = 0; pos < spelling_num_; pos++) {
				double score = log(raw_spellings_[pos].freq) * score_amplifier_;
				assert(score >= 0);

				average_score += score;

				// Because of calculation precision issue, score might be a little bigger
				// than 255 after being amplified.
				if (score > 255) {
					score = 255;
				}
				char *this_spl_buf = spelling_buf_ + pos * spelling_size_;
				this_spl_buf[spelling_size_ - 1] =
				    static_cast<char>((unsigned char)score);

				if (kPrintDebug0) {
					printf("---pos:%zd, %s, psb:%d\n", pos, this_spl_buf,
					       (unsigned char)this_spl_buf[spelling_size_ - 1]);
				}
			}
			average_score /= spelling_num_;
			assert(average_score <= 255);
			average_score_ = static_cast<uint8>(average_score);

			if (kPrintDebug0)
				printf("\n----Score Amplifier: %f, Average Score: %d\n", score_amplifier_,
				       average_score_);
		}

		*item_size = spelling_size_;
		*spl_num = spelling_num_;
		frozen_ = true;
		return spelling_buf_;
	}

	float SpellingTable::get_score_amplifier()
	{
		return static_cast<float>(score_amplifier_);
	}

	unsigned char SpellingTable::get_average_score()
	{
		return average_score_;
	}

#endif  // ___BUILD_MODEL___
}  // namespace ime_pinyin
